/*!
    \copyright  Copyright (c) 2021-2024 Qualcomm Technologies International, Ltd.
    All Rights Reserved.
    Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    \ingroup media_control_client
    \brief   Media control Profile - Client Role Implementation.
*/

#include <logging.h>
#include <panic.h>

#include "gatt_service_discovery.h"
#include "media_control_client.h"
#include "media_control_client_private.h"
#include "media_control_client_ots.h"
#include "pddu_map.h"
#include "device.h"
#include "gatt_connect.h"
#include "bt_device.h"
#include "device_properties.h"
#include "device_db_serialiser.h"
#include "gatt.h"
#include "gatt_service_discovery_lib.h"
#include "synergy.h"
#include "pairing.h"
#include "device_list.h"
#include "event_bus_publish.h"
#include "le_audio_profile_channel_event_spec.h"

#ifdef INCLUDE_LE_AUDIO_UNICAST
/* Move functions to KEEP section such that they are not removed during garbage collection */
#define MEDIA_CTRL_CLIENT_KEEP UNITCODESECTION(KEEP)
#else
#define MEDIA_CTRL_CLIENT_KEEP
#endif

/*! Invalid handle for MCS */
#define MEDIA_CONTROL_CLIENT_INVALID_HANDLE (0xFFFF)

typedef struct
{
    McpMessageId        id;
    McpProfileHandle    prflHndl;         /*! MCP profile handle*/
    ServiceHandle       srvcHndl;         /*! Reference handle for the instance */
    McpStatus           status;           /*! Status of the reading attempt */
} media_control_client_read_cfm_hdr_t;

/*! \brief Action to take on the Media client instance */
typedef void (*media_control_action)(media_control_client_instance_t *instance, void *action_param);

/*! \brief Media control client task data. */
media_control_client_task_data_t media_control_taskdata;

/*! \brief Handler that receives notification from Media Control Profile library */
static void MediaControlClient_HandleMessage(Task task, MessageId id, Message message);

/*! \brief Get the stored handles from NVM. */
static void * mediaControlClient_RetrieveClientHandlesFromNvm(gatt_cid_t cid);

/*! \brief Check if service re-discovery is needed or not. */
static bool mediaControlClient_IsServiceRediscoveryNeeded(gatt_cid_t cid);

/*! \brief Callbacks registered with gatt service discovery module */
static const gatt_service_discovery_callback_t media_control_client_gatt_service_discovery_cb =
{
    .IsServiceRediscoveryNeeded = mediaControlClient_IsServiceRediscoveryNeeded,
};

/*! \brief Publish Connection State Change */
static void mediaControlClient_PublishConnectionStatus(uint32 cid, int status)
{
    le_audio_profile_event_connection_state_change_data_t evt_data;

    evt_data.uuid = LE_AUDIO_MCS_UUID_SERVICE;
    evt_data.connection_handle = GattConnect_GetHandleFromConnectionId(cid);
    evt_data.status = status;
    EventBus_Publish(EVENT_BUS_CHANNEL_LE_AUDIO_MCP_PROFILE, le_audio_profile_event_connection_state_change, &evt_data, sizeof(evt_data));
}

/*! \brief Publish Service Discovered */
static void mediaControlClient_PublishServiceDiscovered(uint32 cid)
{
    le_audio_profile_event_service_discovered_data_t evt_data;

    evt_data.handle = GattConnect_GetHandleFromConnectionId(cid);
    evt_data.status = 0;
    EventBus_Publish(EVENT_BUS_CHANNEL_LE_AUDIO_MCP_PROFILE, le_audio_profile_event_service_discovered, &evt_data, sizeof(evt_data));
}

/*! \brief Publish Read Error */
static void mediaControlClient_PublishReadError(Message message)
{
    media_control_client_read_cfm_hdr_t *read_cfm = (media_control_client_read_cfm_hdr_t *) message;
    media_control_client_instance_t *instance;
    uint16_t uuid = 0;

    switch(read_cfm->id)
    {
        case MCP_GET_MEDIA_PLAYER_NAME_CFM:
            uuid = LE_AUDIO_MCS_UUID_MEDIA_PLAYER_NAME;
        break;

        case MCP_GET_MEDIA_PLAYER_ICON_URL_CFM:
            uuid = LE_AUDIO_MCS_UUID_MEDIA_PLAYER_ICON_URL;
        break;

        case MCP_GET_TRACK_TITLE_CFM:
            uuid = LE_AUDIO_MCS_UUID_TRACK_TITLE;
        break;

        case MCP_GET_TRACK_DURATION_CFM:
            uuid = LE_AUDIO_MCS_UUID_TRACK_DURATION;
        break;

        case MCP_GET_TRACK_POSITION_CFM:
            uuid = LE_AUDIO_MCS_UUID_TRACK_POSITION;
        break;

        case MCP_GET_PLAYBACK_SPEEED_CFM:
            uuid = LE_AUDIO_MCS_UUID_PLAYBACK_SPEED;
        break;

        case MCP_GET_SEEKING_SPEED_CFM:
            uuid = LE_AUDIO_MCS_UUID_SEEKING_SPEED;
        break;

        case MCP_GET_PLAYING_ORDER_CFM:
            uuid = LE_AUDIO_MCS_UUID_PLAYING_ORDER;
        break;

        case MCP_GET_PLAYING_ORDER_SUPPORTED_CFM:
            uuid = LE_AUDIO_MCS_UUID_PLAYING_ORDER_SUPPORTED;
        break;

        case MCP_GET_MEDIA_STATE_CFM:
            uuid = LE_AUDIO_MCS_UUID_MEDIA_STATE;
        break;

        case MCP_GET_SUPPORTED_OPCODES_CFM:
            uuid = LE_AUDIO_MCS_UUID_MEDIA_CONTROL_POINT_OP_SUPP;
        break;

        case MCP_GET_CONTENT_CONTROL_ID_CFM:
            uuid = LE_AUDIO_MCS_UUID_CONTENT_CONTROL_ID;
        break;

        default:
        break;
    }

    if (uuid == 0)
    {
        return;
    }

    instance = MediaControlClient_GetInstance(media_client_compare_by_profile, (unsigned) read_cfm->prflHndl);
    if (read_cfm->status != MCP_STATUS_SUCCESS && instance != NULL)
    {
        le_audio_profile_event_char_read_rsp_data_t event_data;

        event_data.handle = GattConnect_GetHandleFromConnectionId(instance->cid);
        event_data.uuid = uuid;
        event_data.status = (int) read_cfm->status;
        event_data.type = 1;
        event_data.len = 0;
        EventBus_Publish(EVENT_BUS_CHANNEL_LE_AUDIO_MCP_PROFILE, le_audio_profile_event_char_read_rsp, &event_data, sizeof(event_data));
    }
}

/*! \brief Callback function to handle GATT Connect notification */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_OnGattConnect(gatt_cid_t cid)
{
    UNUSED(cid);
}
MEDIA_CTRL_CLIENT_KEEP
/*! \brief Destroy media control profile if any established for this connection */
static void mediaControlClient_OnGattDisconnect(gatt_cid_t cid)
{
    media_control_client_instance_t *instance;

    DEBUG_LOG("mediaControlClient_OnGattDisconnect: cid=0x%04X", cid);
    instance = MediaControlClient_GetInstance(media_client_compare_by_cid, (unsigned)cid);

    if (instance != NULL)
    {
        if (instance->state == media_client_state_discovery)
        {
            /* Initialization is in progress Do not place a destroy request.Just reset the instance */
            MediaControlClient_ResetMediaClientInstance(instance);
        }
        else
        {
            /* If remote_db_changed flag has been set, it means that destroy request has already been placed */
            if (!instance->remote_db_changed)
            {
                McpDestroyReq(instance->mcp_profile_handle);
            }

            if (GattConnect_GetDisconnectReasonCode(instance->cid) != gatt_connect_disconnect_reason_link_transferred)
            {
                mediaControlClient_PublishConnectionStatus(instance->cid, -1);
            }
        }
    }
}

#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
/*! \brief Clear the stored client handles from NVM */
static void mediaControlClient_ClearClientHandles(gatt_cid_t cid)
{
    device_t device = GattConnect_GetBtDevice(cid);

    if (device)
    {
        Device_RemoveProperty(device, device_property_media_control_client);
        DeviceDbSerialiser_SerialiseDevice(device);
    }
}

/*! \brief Handles remote database change indication */
static void mediaControlClient_OnRemoteDbChange(gatt_cid_t cid)
{
    media_control_client_instance_t *instance;

    /* Clear the stored client handles */
    mediaControlClient_ClearClientHandles(cid);

    instance = MediaControlClient_GetInstance(media_client_compare_by_cid, (unsigned)cid);

    if (instance == NULL)
    {
        /* This means we disconnected already. So just acknowledge */
        GattConnect_AcknowledgeRemoteDbChange(cid);
    }
    else
    {
        /* If state is in 'media_client_state_discovery', do not deinit the profile here. Instead
           wait for the initialization to get completed. */
        if (instance->state != media_client_state_discovery)
        {
            /* De-init the profile */
            McpDestroyReq(instance->mcp_profile_handle);
        }

        /* Disconnect is in progress. Wait for to complete before acknowledging */
        instance->remote_db_changed = TRUE;
    }
}
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */

static const gatt_connect_observer_callback_t gatt_media_client_callback =
{
    .OnConnection = mediaControlClient_OnGattConnect,
    .OnDisconnection = mediaControlClient_OnGattDisconnect
};


/*! \brief Function that checks whether the GMCS and OTS handles are already present in NVM */
MEDIA_CTRL_CLIENT_KEEP
static bool mediaControlClient_IsHandlesSameAsStoredData(media_control_client_gatt_device_data_t *current_data,
                                                         unsigned gatt_cid)
{
    media_control_client_gatt_device_data_t *stored_data = NULL;
    bool is_same_as_stored_data = FALSE;

    /* Get the handle information from NVM */
    stored_data = (media_control_client_gatt_device_data_t *) mediaControlClient_RetrieveClientHandlesFromNvm(gatt_cid);

    if (stored_data != NULL &&
        (memcmp(stored_data, current_data, sizeof(media_control_client_gatt_device_data_t)) == 0))
    {
        is_same_as_stored_data = TRUE;
    }

    return is_same_as_stored_data;
}

/*! \brief Function that checks whether the media client instance matches based on the compare type */
MEDIA_CTRL_CLIENT_KEEP
static bool mediaControlClient_Compare(media_instance_compare_by_type_t type,
                                               unsigned compare_value,
                                               media_control_client_instance_t *instance)
{
    bool found = FALSE;

    switch (type)
    {
        case media_client_compare_by_cid:
            found = instance->cid == (gatt_cid_t) compare_value;
        break;

        case media_client_compare_by_profile:
            found = instance->mcp_profile_handle == (McpProfileHandle) compare_value;
        break;

        case media_client_compare_by_state:
            found = instance->state == (media_client_state_t) compare_value;
        break;

        case media_client_compare_by_bdaddr:
        {
            bdaddr addr;
            bdaddr *device_addr = (bdaddr *) compare_value;
            found = instance->state == media_client_state_connected &&
                    GattConnect_GetPublicAddrFromConnectionId(instance->cid, &addr) &&
                    BdaddrIsSame(&addr, device_addr);
        }
        break;

        case media_client_compare_by_valid_invalid_cid :
            found = instance->state == media_client_state_connected &&
                   (instance->cid == (gatt_cid_t) compare_value || compare_value == INVALID_CID);

        default:
        break;
    }

    return found;
}

MEDIA_CTRL_CLIENT_KEEP
static pdd_size_t mediaControlClient_GetDeviceDataLength(device_t device)
{
    void *config = NULL;
    size_t config_size = 0;

    if (!Device_GetProperty(device, device_property_media_control_client, &config, &config_size))
    {
        config_size = 0;
    }
    return config_size;
}

MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_SerialiseDeviceData(device_t device, void *buf, pdd_size_t offset)
{
    void *config = NULL;
    size_t config_size = 0;
    UNUSED(offset);

    if (Device_GetProperty(device, device_property_media_control_client, &config, &config_size))
    {
        memcpy(buf, config, config_size);
    }
}

MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_DeserialiseDeviceData(device_t device, void *buf, pdd_size_t data_length, pdd_size_t offset)
{
    UNUSED(offset);

    Device_SetProperty(device, device_property_media_control_client, buf, data_length);
}

MEDIA_CTRL_CLIENT_KEEP
void MediaControlClient_RegisterAsPersistentDeviceDataUser(void)
{
    DeviceDbSerialiser_RegisterPersistentDeviceDataUser(
        PDDU_ID_LEA_MEDIA_CLIENT_CONTROL,
        mediaControlClient_GetDeviceDataLength,
        mediaControlClient_SerialiseDeviceData,
        mediaControlClient_DeserialiseDeviceData);
}

/*! \brief Write all the media control service handles to NVM */
MEDIA_CTRL_CLIENT_KEEP
void mediaControlClient_WriteMediaDeviceDataToStore(media_control_client_instance_t *instance)
{
    GattMcsClientDeviceData *mcs_handles;

    /* Retrieve all the discovered media control service handles */
    mcs_handles = McpGetMediaPlayerAttributeHandles(instance->mcp_profile_handle, instance->mcs_service_handle);

    /* Try to store the GMCS and OTS handle information in NVM */
    if (mcs_handles != NULL)
    {
        media_control_client_gatt_device_data_t *mcs_ots_data = PanicUnlessMalloc(sizeof(*mcs_ots_data));

        mcs_ots_data->mcs_dev_data = *mcs_handles;
        mediaControlClient_GetOtsAttributeHandles(instance, &mcs_ots_data->ots_dev_data);

        if (!mediaControlClient_IsHandlesSameAsStoredData(mcs_ots_data, instance->cid))
        {
            DEBUG_LOG("mediaControlClient_WriteMediaDeviceDataToStore");
            MediaControlClient_StoreClientHandles(instance->cid,
                                                  (void *) mcs_ots_data,
                                                  sizeof(media_control_client_gatt_device_data_t));
        }

        pfree(mcs_handles);
        pfree(mcs_ots_data);
    }
}

/*! \brief Upon a successful initialization of MCP, preserve the information in media client instance */
MEDIA_CTRL_CLIENT_KEEP
static void  mediaControlClient_HandleMediaProfileInitConfirmation(const McpInitCfm *message)
{
    media_control_client_instance_t *instance;

    instance = MediaControlClient_GetInstance(media_client_compare_by_profile, message->prflHndl);

    DEBUG_LOG("mediaControlClient_HandleMediaProfileInitConfirmation prfl_handle: 0x%x, status: %d, instance: %p",
            message->prflHndl, message->status, instance);

    if (!instance)
    {
        /* Instance not found; find an unused slot */
        instance = MediaControlClient_GetInstance(media_client_compare_by_profile, 0);
        DEBUG_LOG_VERBOSE("mediaControlClient_HandleMediaProfileInitConfirmation: new instance %p", instance);
    }

    DEBUG_LOG("mediaControlClient_HandleMediaProfileInitConfirmation: status: %d", message->status);

    if (instance != NULL &&
        message->status == MCP_STATUS_SUCCESS &&
        instance->mcp_profile_handle == message->prflHndl &&
        !instance->remote_db_changed)
    {
        PanicFalse(message->mcsInstCount == 1);
        instance->mcs_service_handle = *message->mcsSrvcHandle;
        instance->state = media_client_state_connected;

        if (!instance->handover_in_progress)
        {
            DEBUG_LOG("mediaControlClient_HandleMediaProfileInitConfirmation: Registering for Notifications");
            McpRegisterForNotificationReq(instance->mcp_profile_handle,
                                          instance->mcs_service_handle,
                                          MCS_MEDIA_STATE_POS12 | MCS_MEDIA_CONTROL_POINT_POS13,
                                          MCS_MEDIA_STATE_POS12 | MCS_MEDIA_CONTROL_POINT_POS13);

            McpGetMediaPlayerAttribute(instance->mcp_profile_handle,
                                       instance->mcs_service_handle,
                                       MCS_CONTENT_CONTROL_ID);
            McpGetMediaPlayerAttribute(instance->mcp_profile_handle,
                                       instance->mcs_service_handle,
                                       MCS_MEDIA_STATE);

            mediaControlClient_PublishConnectionStatus(instance->cid, 0);
        }
        else
        {
            instance->handover_in_progress = FALSE;
        }

        pfree(message->mcsSrvcHandle);

        mediaControlClient_OtsConnect(instance);

        mediaControlClient_WriteMediaDeviceDataToStore(instance);

    }
    else if (message->status == MCP_STATUS_IN_PROGRESS &&
             instance != NULL &&
             instance->mcp_profile_handle == 0)
    {
        instance->mcp_profile_handle = message->prflHndl;
    }
    else
    {
        if (message->status == MCP_STATUS_SUCCESS &&
            (instance == NULL || instance->mcp_profile_handle != message->prflHndl || instance->remote_db_changed))
        {
            /* Profile initialization is successful, but there could be below conditions:
             * 1. Instance is NULL, which indicates the GATT connection has already gone.
             *                             (OR)
             * 2. Instance is not NULL, but profile handles mismatch.This could happen
             *    if the init cfm has arrived very late, while we are already processing
             *    a new init request.Though a remote possibility, it can happen
             *                             (OR)
             * 3. Remote database got changed while we initializing MCP.
             *
             *    Under all above conditions place a destroy request immediately.
             */
            McpDestroyReq(message->prflHndl);
        }
        else
        {
#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
            /* Acknowledge if remote database changed while initialization */
            if (instance != NULL && instance->remote_db_changed)
            {
                GattConnect_AcknowledgeRemoteDbChange(instance->cid);
            }
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */

            /* An error occurred during MCP internal discovery. Reset the Media client instance */
            MediaControlClient_ResetMediaClientInstance(instance);
        }
    }
}

/*! \brief Upon receiving a destroy confirmation, preserve the handles in NVM */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_HandleMediaProfileDestroyConfirmation(const McpDestroyCfm *message)
{
    media_control_client_instance_t *media_client;

    /* Find the matching media client instance based on the profile handle */
    media_client = MediaControlClient_GetInstance(media_client_compare_by_profile, (unsigned)message->prflHndl);

    DEBUG_LOG("mediaControlClient_HandleMediaProfileDestroyConfirmation prfl_handle: 0x%x, status: %d, instance: %p",
            message->prflHndl, message->status, media_client);

    if (media_client != NULL && message->status != MCP_STATUS_IN_PROGRESS)
    {
#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
        /* Acknowledge if destroy is due to remote database change */
        if (media_client->remote_db_changed)
        {
            GattConnect_AcknowledgeRemoteDbChange(media_client->cid);
        }
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */

        /* Reset the media client instance */
        MediaControlClient_ResetMediaClientInstance(media_client);
    }
}

static void mediaControlClient_HandleMediaPlayerAttributeConfirmation(const McpSetMediaPlayerAttributeCfm *message)
{
    media_control_client_instance_t *media_client =
        MediaControlClient_GetInstance(media_client_compare_by_profile, (unsigned)message->prflHndl);

    if (media_client != NULL)
    {
        le_audio_profile_event_char_write_rsp_data_t event_data;

        event_data.handle = GattConnect_GetHandleFromConnectionId(media_client->cid);
        event_data.uuid = message->charac + LE_AUDIO_MCS_UUID_MEDIA_PLAYER_NAME - 1;
        if (message->charac == MCS_CONTENT_CONTROL_ID)
        {
            event_data.uuid = LE_AUDIO_MCS_UUID_CONTENT_CONTROL_ID;
        }
        event_data.status = message->status;
        EventBus_Publish(EVENT_BUS_CHANNEL_LE_AUDIO_MCP_PROFILE, le_audio_profile_event_char_write_rsp, &event_data, sizeof(event_data));
    }
}

/*! \brief Handle control point write confirmation */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_HandleControlPointWriteConfirmation(const McpSetMediaControlPointCfm* message)
{
    DEBUG_LOG("mediaControlClient_HandleControlPointWriteConfirmation Opcode %d Status:%d", message->op, message->status);
}

void mediaControlClient_RegisterForMediaStateChangeIndications(media_control_client_callback_if * callback_if)
{
    media_control_mcp_state_client_t * new_client = PanicUnlessMalloc(sizeof(media_control_mcp_state_client_t));
    memset(new_client,0,sizeof(media_control_mcp_state_client_t));
    new_client->callback_if.media_state_change_callback = callback_if->media_state_change_callback;

    new_client->next = media_control_taskdata.clients;

    media_control_taskdata.clients = new_client;
}

static void mediaControlClient_SendIndicationToMediaStateClients(media_control_client_instance_t * media_client)
{
    media_control_mcp_state_client_t * curr = media_control_taskdata.clients;

    while(curr)
    {
        if(curr->callback_if.media_state_change_callback)
        {
            curr->callback_if.media_state_change_callback(media_client->server_state, media_client->cid);
        }

        curr = curr->next;
    }

}

/*! \brief Handle remote media state change notification */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_HandleMediaStateNotification(const McpMediaStateInd *message)
{
    media_control_client_instance_t *media_client;

    media_client = MediaControlClient_GetInstance(media_client_compare_by_profile, (unsigned)message->prflHndl);
    if (media_client == NULL)
    {
        Panic();
        return;
    }

    DEBUG_LOG_INFO("mediaControlClient_HandleMediaStateNotification Media State %d For Cid 0x%x",
                   message->mediaState, media_client->cid);

    media_client->server_state = message->mediaState;

    if(media_client->server_state == media_server_state_playing)
    {
        tp_bdaddr tp_addr = {0};
        device_t device = GattConnect_GetBtLeDevice(media_client->cid);

        if(BtDevice_GetTpBdaddrForDevice(device, &tp_addr))
        {
            DEBUG_LOG("mediaControlClient_HandleMediaStateNotification update MRU device %p", device);

            mediaControlClient_SendIndicationToMediaStateClients(media_client);

        }
    }
}

/*! \brief Handle remote media state read confirmation */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_HandleMediaStateConfirmation(const McpGetMediaStateCfm *message)
{
    media_control_client_instance_t *media_client;

    media_client = MediaControlClient_GetInstance(media_client_compare_by_profile, (unsigned)message->prflHndl);
    if (media_client == NULL)
    {
        Panic();
        return;
    }

    DEBUG_LOG("mediaControlClient_HandleMediaStateConfirmation Media State %d For Cid 0x%x status 0x%x",
              message->mediaState, media_client->cid, message->status);

    if (message->status == MCP_STATUS_SUCCESS)
    {
        media_client->server_state = message->mediaState;
    }
}

MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_HandleGetContentControlIdCfm(const McpGetContentControlIdCfm *message)
{
    media_control_client_instance_t *media_client;

    media_client = MediaControlClient_GetInstance(media_client_compare_by_profile, (unsigned)message->prflHndl);
    PanicNull(media_client);

    if (message->status == MCP_STATUS_SUCCESS)
    {
        media_client->content_control_id = message->ccid;
    }
}

MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_HandleNotificationConfirmation(const McpNtfCfm *message)
{
    DEBUG_LOG("mediaControlClient_HandleNotificationConfirmation Result 0x%x", message->status);
}

#ifdef INCLUDE_GATT_OTS_CLIENT_MODULE

static void mediaControlClient_HandleInitOptServicesCfm(const McpInitOptServicesCfm *message)
{
    mediaControlClient_HandleInitOptServicesCfmForOts(message);

    /* Any other optional services addition would require separate handler called similar to above OTS handler */
}

#endif

/*! \brief Process notifications received from MCP library */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_HandleMcpMessage(Message message)
{
    McpMessageId mcpId = *(McpMessageId*)message;
    void *free_it = NULL;

    DEBUG_LOG_INFO("mediaControlClient_HandleMcpMessage Received Message Id : 0x%x", mcpId);

    switch (mcpId)
    {
        case MCP_INIT_CFM:
            mediaControlClient_HandleMediaProfileInitConfirmation((const McpInitCfm *) message);
        break;

        case MCP_DESTROY_CFM:
            mediaControlClient_HandleMediaProfileDestroyConfirmation((const McpDestroyCfm *) message);
        break;

        case MCP_SET_MEDIA_PLAYER_ATTRIBUTE_CFM:
            mediaControlClient_HandleMediaPlayerAttributeConfirmation((const McpSetMediaPlayerAttributeCfm *) message);
        break;

        case MCP_SET_MEDIA_CONTROL_POINT_CFM:
            mediaControlClient_HandleControlPointWriteConfirmation((const McpSetMediaControlPointCfm *) message);
        break;

        case MCP_MEDIA_STATE_IND:
            mediaControlClient_HandleMediaStateNotification((const McpMediaStateInd *) message);
        break;

        case MCP_GET_MEDIA_STATE_CFM:
            mediaControlClient_HandleMediaStateConfirmation((const McpGetMediaStateCfm *) message);
        break;

        case MCP_TRACK_CHANGED_IND:
            DEBUG_LOG("mediaControlClient_HandleMcpMessage : Media Track Changed");
        break;

        case MCP_NTF_CFM:
            mediaControlClient_HandleNotificationConfirmation((const McpNtfCfm *) message);
        break;

        case MCP_GET_MEDIA_PLAYER_NAME_CFM:
            free_it = ((const McpGetMediaPlayerNameCfm *) message)->name;
        break;

        case MCP_GET_MEDIA_PLAYER_ICON_URL_CFM:
            free_it = ((const McpGetMediaPlayerIconUrlCfm *) message)->iconUrl;
        break;

        case MCP_GET_TRACK_TITLE_CFM:
            free_it = ((const McpGetTrackTitleCfm *) message)->trackTitle;
        break;

        case MCP_GET_CONTENT_CONTROL_ID_CFM:
            mediaControlClient_HandleGetContentControlIdCfm((const McpGetContentControlIdCfm *) message);
        break;

        case MCP_MEDIA_PLAYER_NAME_IND:
            free_it = ((const McpMediaPlayerNameInd *) message)->name;
        break;

        case MCP_TRACK_TITLE_IND:
            free_it = ((const McpTrackTitleInd *) message)->trackTitle;
        break;

#ifdef INCLUDE_GATT_OTS_CLIENT_MODULE

        case MCP_INIT_OPT_SERVICES_CFM:
            mediaControlClient_HandleInitOptServicesCfm((const McpInitOptServicesCfm *) message);
            if (((const McpInitOptServicesCfm *) message)->optServiceInfoCount != 0)
            {
                free_it = ((const McpInitOptServicesCfm *) message)->optSerInfo;
            }
        break;

        case MCP_MEDIA_PLAYER_ICON_OBJ_ID_CFM:
        case MCP_CURRENT_TRACK_SEGMENTS_OBJ_ID_CFM:
        case MCP_CURRENT_TRACK_OBJ_ID_CFM:
        case MCP_NEXT_TRACK_OBJ_ID_CFM:
        case MCP_CURRENT_GROUP_OBJ_ID_CFM:
        case MCP_PARENT_GROUP_OBJ_ID_CFM:
        case MCP_SEARCH_RESULT_OBJ_ID_CFM:
            mediaControlClient_HandleTrackObjectIdCfm((const McpGetObjectIdCfm *) message);
        break;

        case MCP_GET_OBJECT_CONTENTS_BY_OBJ_ID_CFM:
            mediaControlClient_HandleObjContentsByObjectIdCfm((const McpGetObjectContentsByObjIdCfm *) message);
            free_it = ((const McpGetObjectContentsByObjIdCfm *) message)->data;
        break;

        case MCP_DISCOVER_OBJECT_TYPE_IND:
        case MCP_DISCOVER_OBJECT_SIZE_IND:
        case MCP_DISCOVER_OBJECT_ID_IND:
        case MCP_DISCOVER_OBJECT_PROP_IND:
        case MCP_DISCOVER_OBJECT_NAME_IND:
            mediaControlClient_HandleDiscoverObjectCommonInd((const McpGetObjectMetadataCommonInd *) message);
            free_it = ((const McpGetObjectMetadataCommonInd *) message)->value;
        break;

        case MCP_START_DISCOVER_OBJECT_CFM:
        case MCP_STOP_DISCOVER_OBJECT_CFM:
            mediaControlClient_HandleDiscoverObjectCommonCfm((const McpDiscoverObjectCommonCfm *) message);
        break;

        case MCP_GET_CURRENT_MEDIA_OBJECT_TYPE_CFM:
        case MCP_GET_CURRENT_MEDIA_OBJECT_SIZE_CFM:
        case MCP_GET_CURRENT_MEDIA_OBJECT_ID_CFM:
        case MCP_GET_CURRENT_MEDIA_OBJECT_PROP_CFM:
        case MCP_GET_CURRENT_MEDIA_OBJECT_NAME_CFM:
            mediaControlClient_HandleCurrentMediaObjectMetadata((const McpGetObjectMetadataCommonCfm *) message);
            free_it = ((const McpGetObjectMetadataCommonCfm *) message)->value;
        break;

        case MCP_OTS_IND_CFM:
            mediaControlClient_HandleOtsIndCfm((const McpOtsIndCfm *) message);
        break;

        case MCP_OBJECT_CHANGED_IND:
            mediaControlClient_HandleObjectChangedInd((const McpObjectChangedInd *) message);
        break;

#endif /* INCLUDE_GATT_OTS_CLIENT_MODULE */

        default:
            DEBUG_LOG("mediaControlClient_HandleMcpMessage Media Message Received ID : 0x%x", mcpId);
        break;
    }

    mediaControlClient_PublishReadError(message);

    if (free_it != NULL)
    {
        pfree(free_it);
    }
}

/*! \brief Create the Media Control Client Instance */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_CreateInstance(gatt_cid_t cid)
{
    McpInitData client_init_params;
    media_control_client_instance_t *instance = NULL;
    McpHandles mcp_handle_data;

    memset(&mcp_handle_data, 0, sizeof(McpHandles));
    client_init_params.cid = cid;

    instance = MediaControlClient_GetInstance(media_client_compare_by_state, media_client_state_idle);

    if (instance != NULL)
    {
        instance->cid = cid;
        instance->state = media_client_state_discovery;
        /* mcsInstCount should always be 1 as we are interested only with GMCS Service */
        mcp_handle_data.mcsInstCount = 1;
        mcp_handle_data.mcsHandle = (GattMcsClientDeviceData*)MediaControlClient_RetrieveClientHandles(cid);
        McpInitReq(TrapToOxygenTask((Task)&media_control_taskdata.task_data),
                   &client_init_params,
                   mcp_handle_data.mcsHandle == NULL ? NULL : &mcp_handle_data);
    }
}

MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_WriteInvalidClientHandles(gatt_cid_t cid)
{
    media_control_client_gatt_device_data_t mcs_ots_data = { 0 };

    mcs_ots_data.mcs_dev_data.startHandle = MEDIA_CONTROL_CLIENT_INVALID_HANDLE;
    mcs_ots_data.mcs_dev_data.endHandle = MEDIA_CONTROL_CLIENT_INVALID_HANDLE;

    /* Store the invalid MCP handle information in NVM */
     if (!mediaControlClient_IsHandlesSameAsStoredData(&mcs_ots_data, cid))
    {
        DEBUG_LOG("mediaControlClient_WriteInvalidClientHandles cid 0x%x", cid);
        MediaControlClient_StoreClientHandles(cid,
                                              (void *) &mcs_ots_data,
                                              sizeof(media_control_client_gatt_device_data_t));
    }
}

/*! \brief If MCS Service is discovered successfully, initialize the Media control profile */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_HandleServiceRange(const GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM_T *message)
{
    bool mcs_service_present = FALSE;

    DEBUG_LOG("mediaControlClient_HandleServiceRange Number of LEA Services Found: %d, result 0x%x", message->srvcInfoCount, message->result);

    /* Initialize MCP only when there is only one instance GMCS service in the remote server */
    if (message->result == GATT_SD_RESULT_SUCCESS && message->srvcInfoCount != 0)
    {
        if (message->srvcInfoCount == 1 && (message->srvcInfo->srvcId & GATT_SD_GMCS_SRVC))
        {
            DEBUG_LOG("mediaControlClient_HandleServiceRange MCS Service found in Remote CID: %d", message->cid);
            mediaControlClient_CreateInstance(message->cid);

            mediaControlClient_PublishServiceDiscovered(message->cid);
            mcs_service_present = TRUE;
        }
        else
        {
            DEBUG_LOG("mediaControlClient_HandleServiceRange unique GMCS Service not found in Remote Server CID: %d", message->cid);
        }

        pfree(message->srvcInfo);
    }

    if (!mcs_service_present &&
        (message->result == GATT_SD_RESULT_SUCCESS || message->result == GATT_SD_RESULT_SRVC_ID_NOT_FOUND))
    {
        /* GMCS is not present in remote server. Write invalid handles to NVM so that we don't trigger unnecessary service
           rediscovery on mediaControlClient_IsServiceRediscoveryNeeded().  */
        mediaControlClient_WriteInvalidClientHandles(message->cid);
    }
}

/*! \brief Handler to handle GATT Service Discovery related primitives */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_HandleGattPrim(Message message)
{
    switch (*(CsrBtCmPrim *)message)
    {
        case GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM:
            mediaControlClient_HandleServiceRange((const GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM_T*)message);
        break;

        default:
        break;
    }
}

/*! \brief Check if the Media control service is available in remote server and is discovered */
MEDIA_CTRL_CLIENT_KEEP
void MediaControlClient_SetServiceStatusForCid(gatt_cid_t cid, bool service_not_found)
{
    DEBUG_LOG("MediaControlClient_SetServiceStatusForCid cid: 0x%x service_not_found %d", cid, service_not_found);

    if (!service_not_found)
    {
        if (MediaControlClient_RetrieveClientHandles(cid) != NULL)
        {
            DEBUG_LOG("MediaControlClient_SetServiceStatusForCid create instance");
            /* If handles are already there, it means we can directly create the instance */
            mediaControlClient_CreateInstance(cid);
            mediaControlClient_PublishServiceDiscovered(cid);
        }
        else
        {
            /* Client handles not exists, find the service range first before creating instance */
            DEBUG_LOG("MediaControlClient_SetServiceStatusForCid start find service range");
            GattServiceDiscoveryFindServiceRange(TrapToOxygenTask((Task)&media_control_taskdata.task_data),
                                                 cid,
                                                 GATT_SD_GMCS_SRVC);
        }
    }
    else
    {
        /* GMCS is not present in remote server. Write invalid handles to NVM so that we don't trigger unnecessary service
           rediscovery on mediaControlClient_IsServiceRediscoveryNeeded().  */
        mediaControlClient_WriteInvalidClientHandles(cid);
    }
}

/*! \brief When pairing completes with handset, register for media state notifications */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_HandlePairingActivity(const PAIRING_ACTIVITY_T *message)
{
    media_control_client_instance_t *instance = NULL;

    if (message->status != pairingActivitySuccess)
    {
        return;
    }

    instance = MediaControlClient_GetInstance(media_client_compare_by_bdaddr, (unsigned)&message->device_addr);

    if (instance != NULL)
    {
        DEBUG_LOG("mediaControlClient_HandlePairingActivity Registering for CID 0x%x", instance->cid);
        McpRegisterForNotificationReq(instance->mcp_profile_handle,
                                      instance->mcs_service_handle,
                                      MCS_MEDIA_STATE_POS12 | MCS_MEDIA_CONTROL_POINT_POS13,
                                      MCS_MEDIA_STATE_POS12 | MCS_MEDIA_CONTROL_POINT_POS13);

        McpGetMediaPlayerAttribute(instance->mcp_profile_handle,
                                   instance->mcs_service_handle,
                                   MCS_CONTENT_CONTROL_ID);

        mediaControlClient_WriteMediaDeviceDataToStore(instance);
    }
}

/*! \brief Process notifications received for Media control client task */
MEDIA_CTRL_CLIENT_KEEP
static void MediaControlClient_HandleMessage(Task task, MessageId id, Message message)
{
    UNUSED(task);
    DEBUG_LOG("MediaControlClient_HandleMessage Received Message Id : 0x%x", id);

    switch (id)
    {
        case MCP_PROFILE_PRIM:
            mediaControlClient_HandleMcpMessage(message);
        break;

        case GATT_SD_PRIM:
            mediaControlClient_HandleGattPrim(message);
        break;

        case PAIRING_ACTIVITY:
            mediaControlClient_HandlePairingActivity((const PAIRING_ACTIVITY_T*)message);
        break;

        default:
        break;
    }
}

/*! \brief Action function that sends the opcode to the remote MCS Server */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlPoint_SetOpcode(media_control_client_instance_t *instance, void *action_param)
{
    media_control_set_t *set_value = (media_control_set_t *) action_param;

    DEBUG_LOG_INFO("mediaControlPoint_SetOpcode Opcode:0x%x cid: 0x%x", set_value->op, instance->cid);
    McpSetMediaControlPoint(instance->mcp_profile_handle, instance->mcs_service_handle, set_value->op, set_value->val);
}

/*! \brief Action function that toggles the media state on the remote MCS Server */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlPoint_ToggleMediaState(media_control_client_instance_t *instance, void *action_param)
{
    GattMcsOpcode opcode = (instance->server_state == media_server_state_playing ||
                            instance->server_state == media_server_state_seeked) ? GATT_MCS_CLIENT_PAUSE : GATT_MCS_CLIENT_PLAY;

    UNUSED(action_param);

    DEBUG_LOG("mediaControlPoint_ToggleMediaState Opcode:0x%x, cid:0x%x", opcode, instance->cid);
    McpSetMediaControlPoint(instance->mcp_profile_handle, instance->mcs_service_handle, opcode, 0);
}

/*! \brief Executes the assigned action on the connected media profile */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlClient_ActionOnConnectedProfile(gatt_cid_t cid, media_control_action action_fn, void *action_param)
{
    media_control_client_instance_t *instance = NULL;

    for (instance = &media_control_taskdata.media_client_instance[0];
         instance < &media_control_taskdata.media_client_instance[MAX_MEDIA_SERVER_SUPPORTED];
         instance++)
    {
        if (instance->state == media_client_state_connected &&
            (cid == INVALID_CID || cid == instance->cid))
        {
            action_fn(instance, action_param);

            if (instance->cid == cid)
            {
                break;
            }
        }
    }
}

/*! \brief Action function that gets the media player attribute from remote server */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlPoint_GetMediaPlayerAttribute(media_control_client_instance_t *instance, void *action_param)
{
    media_control_attrib_t *get_value = (media_control_attrib_t *) action_param;

    DEBUG_LOG("mediaControlPoint_GetMediaPlayerAttribute Attribute:0x%x cid: 0x%x", get_value->attrib, instance->cid);
    McpGetMediaPlayerAttribute(instance->mcp_profile_handle, instance->mcs_service_handle, get_value->attrib);
}

/*! \brief Action function that sets the media player attribute in remote server */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlPoint_SetMediaPlayerAttribute(media_control_client_instance_t *instance, void *action_param)
{
    media_control_attrib_t *set_value = (media_control_attrib_t *) action_param;

    DEBUG_LOG("mediaControlPoint_SetMediaPlayerAttribute Opcode:0x%x cid: 0x%x", set_value->attrib, instance->cid);
    McpSetMediaPlayerAttribute(instance->mcp_profile_handle,
                               instance->mcs_service_handle,
                               set_value->attrib,
                               set_value->len,
                               set_value->val);
}

/*! \brief Action function that sends the notifications registration request */
MEDIA_CTRL_CLIENT_KEEP
static void mediaControlPoint_SendNotificationRegisterRequest(media_control_client_instance_t *instance, void *action_param)
{
    media_control_notification_req_t *register_req = (media_control_notification_req_t *) action_param;

    DEBUG_LOG("mediaControlPoint_SendNotificationRegisterRequest cid: 0x%x", instance->cid);
    McpRegisterForNotificationReq(instance->mcp_profile_handle,
                                  instance->mcs_service_handle,
                                  register_req->attrib,
                                  register_req->notif_value);
}

/*! \brief Callback for checking service rediscovery is needed or not */
MEDIA_CTRL_CLIENT_KEEP
static bool mediaControlClient_IsServiceRediscoveryNeeded(gatt_cid_t cid)
{
    /* If there is no client handles in NVM, then that is an indication that GMCS service discovery
       is not yet initiated. So request for rediscovery in that case.
       Note that if GMCS is not in remote server, then the client handles will not be NULL (it will be
       invalid though) and we will not request for re-discovery. */
    return mediaControlClient_RetrieveClientHandlesFromNvm(cid) == NULL;
}

/*! \brief Register with GATT LEA Service discovery */
MEDIA_CTRL_CLIENT_KEEP
void MediaControlClient_Init(void)
{
    DEBUG_LOG("MediaControlClient_Init");
    memset(&media_control_taskdata, 0, sizeof(media_control_taskdata));
    media_control_taskdata.task_data.handler = MediaControlClient_HandleMessage;

    GattConnect_RegisterObserver(&gatt_media_client_callback);
    Pairing_ActivityClientRegister(&media_control_taskdata.task_data);
    GattServiceDiscovery_RegisterServiceForDiscovery(GATT_SD_GMCS_SRVC);
    GattServiceDiscovery_RegisterForCallback(&media_control_client_gatt_service_discovery_cb);
#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
    GattConnect_RegisterRemoteDbChangeCallback(mediaControlClient_OnRemoteDbChange);
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */
}

/*! \brief Send the media control opcode to the remote server */
MEDIA_CTRL_CLIENT_KEEP
void MediaControlClient_SendMediaControlOpcode(gatt_cid_t cid, GattMcsOpcode op, int32 val)
{
    media_control_set_t set_value;

    set_value.op = op;
    set_value.val = val;
    mediaControlClient_ActionOnConnectedProfile(cid, mediaControlPoint_SetOpcode, &set_value);
}

/*! \brief Function that toggles and sends Play/Pause opcode to media server */
MEDIA_CTRL_CLIENT_KEEP
void MediaControlClient_TogglePlayPause(gatt_cid_t cid)
{
    mediaControlClient_ActionOnConnectedProfile(cid, mediaControlPoint_ToggleMediaState, NULL);
}

/*! \brief Function that finds a matching audio context based on the present media state */
MEDIA_CTRL_CLIENT_KEEP
audio_source_provider_context_t MediaClientControl_GetAudioSourceContext(gatt_cid_t cid)
{
    audio_source_provider_context_t audio_context = context_audio_disconnected;
    media_control_client_instance_t *media_client = MediaControlClient_GetInstance(media_client_compare_by_cid, (unsigned) cid);

    if (media_client != NULL)
    {
        if (media_client->server_state == media_server_state_playing ||
            media_client->server_state == media_server_state_seeked)
        {
            audio_context = context_audio_is_playing;
        }
        else if (media_client->server_state == media_server_state_paused)
        {
            audio_context = context_audio_is_paused;
        }
        else
        {
            audio_context = context_audio_connected;
        }
    }

    if (audio_context == context_audio_disconnected &&
        MediaControlClient_GetInstance(media_client_compare_by_state, (unsigned)media_client_state_connected) != NULL)
    {
        audio_context = context_audio_connected;
    }

    return audio_context;
}

/*! \brief Function that reads the Media player attribute */
MEDIA_CTRL_CLIENT_KEEP
void MediaControlClient_GetMediaPlayerAttribute(gatt_cid_t cid, MediaPlayerAttribute charac)
{
    media_control_attrib_t get_value;

    get_value.attrib = charac;
    get_value.val = NULL;
    get_value.len = 0;
    mediaControlClient_ActionOnConnectedProfile(cid, mediaControlPoint_GetMediaPlayerAttribute, &get_value);
}

uint16 MediaControlClient_GetCachedMediaPlayerAttribute(gatt_cid_t cid, MediaPlayerAttribute charac, uint8 **pp_data)
{
    uint16 len = 0;
    media_control_client_instance_t *instance;
    uint8 *data = NULL;

    instance = MediaControlClient_GetInstance(media_client_compare_by_cid, (unsigned) cid);

    if (instance != NULL)
    {
        switch (charac)
        {
            case MCS_MEDIA_STATE:
                data = malloc(sizeof(uint8));
                if (data != NULL)
                {
                    len = 1;
                    data[0] = (uint8) instance->server_state;
                }
            break;

            case MCS_CONTENT_CONTROL_ID:
                data = malloc(sizeof(uint8));
                if (data != NULL)
                {
                    len = 1;
                    data[0] = (uint8) instance->content_control_id;
                }
            break;

            default:
            break;
        }
    }

    *pp_data = data;
    DEBUG_LOG("MediaControlClient_GetCachedMediaPlayerAttribute cid: 0x%x type enum:MediaPlayerAttribute:%d, len: %d", cid, charac, len);

    return len;
}

/*! \brief Function that write into the Media player attribute */
MEDIA_CTRL_CLIENT_KEEP
void MediaControlClient_SetMediaPlayerAttribute(gatt_cid_t cid,
                                                MediaPlayerAttribute charac,
                                                uint16 len,
                                                uint8 *val)
{
    media_control_attrib_t set_value;

    set_value.attrib = charac;
    set_value.val = val;
    set_value.len = len;
    mediaControlClient_ActionOnConnectedProfile(cid, mediaControlPoint_SetMediaPlayerAttribute, &set_value);
}

/*! \brief Register for Media Server notifications */
MEDIA_CTRL_CLIENT_KEEP
void MediaControlClient_RegisterForNotifications(gatt_cid_t cid,
                                                 MediaPlayerAttributeMask characType,
                                                 uint32 notif_value)
{
    media_control_notification_req_t notif_req;

    notif_req.attrib = characType;
    notif_req.notif_value = notif_value;
    mediaControlClient_ActionOnConnectedProfile(cid, mediaControlPoint_SendNotificationRegisterRequest, &notif_req);
}

/*! \brief Get the Media client instance based on the compare type */
MEDIA_CTRL_CLIENT_KEEP
media_control_client_instance_t * MediaControlClient_GetInstance(media_instance_compare_by_type_t type, unsigned cmp_value)
{
    media_control_client_instance_t *instance = NULL;

    for (instance = &media_control_taskdata.media_client_instance[0];
         instance < &media_control_taskdata.media_client_instance[MAX_MEDIA_SERVER_SUPPORTED];
         instance++)
    {
        if (mediaControlClient_Compare(type, cmp_value, instance))
        {
            return instance;
        }
    }

    return NULL;
}

/*! \brief Get the stored handles from NVM. It could be either
           a) valid handles if GTBS handles discovered before
           b) Invalid handles if GTBS is not found in remote server
           c) NULL if we have not yet triggered for GTBS handle discovery
 */
MEDIA_CTRL_CLIENT_KEEP
static void * mediaControlClient_RetrieveClientHandlesFromNvm(gatt_cid_t cid)
{
    device_t device = GattConnect_GetBtDevice(cid);
    void *server_handle_info = NULL;

    if (device)
    {
        size_t size;

        if (!Device_GetProperty(device, device_property_media_control_client, &server_handle_info, &size))
        {
            server_handle_info = NULL;
        }
    }

    DEBUG_LOG("mediaControlClient_RetrieveClientHandlesFromNvm %p", server_handle_info);

    return server_handle_info;
}

/*! \brief Retrieve GMCS handle data from NVM for the provided connection identifier */
MEDIA_CTRL_CLIENT_KEEP
void * MediaControlClient_RetrieveClientHandles(gatt_cid_t cid)
{
    media_control_client_gatt_device_data_t *stored_data = mediaControlClient_RetrieveClientHandlesFromNvm(cid);

    if (stored_data != NULL &&
        (stored_data->mcs_dev_data.startHandle != MEDIA_CONTROL_CLIENT_INVALID_HANDLE ||
         stored_data->mcs_dev_data.endHandle != MEDIA_CONTROL_CLIENT_INVALID_HANDLE))
    {
        /* Only return if handles are valid. */
        return stored_data;
    }

    return NULL;
}

/*! \brief Store GMCS handle data to NVM for the provided connection identifier */
MEDIA_CTRL_CLIENT_KEEP
bool MediaControlClient_StoreClientHandles(gatt_cid_t cid, void *config, uint8 size)
{
    bool handles_stored = FALSE;
    device_t device = GattConnect_GetBtDevice(cid);

    if (device)
    {
        Device_SetProperty(device, device_property_media_control_client, config, size);
        DeviceDbSerialiser_SerialiseDevice(device);
        handles_stored = TRUE;
    }

    return handles_stored;
}

/* Get the content control id for media client */
MEDIA_CTRL_CLIENT_KEEP
uint8 MediaClientControl_GetContentControlId(gatt_cid_t cid)
{
    media_control_client_instance_t *instance;
    uint16 content_control_id = 0;

    instance = MediaControlClient_GetInstance(media_client_compare_by_cid, (unsigned)cid);

    if (instance != NULL)
    {
        content_control_id = instance ->content_control_id;
    }

    return content_control_id;
}

/*! \brief Reset the provided media client instance */
MEDIA_CTRL_CLIENT_KEEP
void MediaControlClient_ResetMediaClientInstance(media_control_client_instance_t *media_client)
{
    if (media_client != NULL)
    {
        memset(media_client, 0, sizeof(media_control_client_instance_t));
        media_client->cid = INVALID_CID;
    }
}

MEDIA_CTRL_CLIENT_KEEP
device_t MediaClientControl_GetDeviceForAudioSource(audio_source_t source)
{
    return DeviceList_GetFirstDeviceWithPropertyValue(device_property_le_audio_source, &source, sizeof(audio_source_t));
}

MEDIA_CTRL_CLIENT_KEEP
bool MediaClientControl_IsConnected(gatt_cid_t cid)
{
    return (MediaControlClient_GetInstance(media_client_compare_by_cid, (unsigned) cid) != NULL);
}

#ifdef USE_SYNERGY
bool MediaClientControl_IsMcpConnected(void)
{
    media_control_client_instance_t *instance;

    instance = MediaControlClient_GetInstance(media_client_compare_by_valid_invalid_cid, (unsigned)INVALID_CID);

    if (instance != NULL)
    {
        return TRUE;
    }

    return FALSE;
}
#endif /* USE_SYNERGY */
